<!DOCTYPE html>


<html lang="zh-CN">


<head>
  <meta charset="utf-8" />
    
  <meta name="description" content="迎着朝阳的博客" />
  
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    Redis基本介绍 |  迎着朝阳
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="https://dxysun.com/static/yan.png" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">

  
<link rel="stylesheet" href="/css/custom.css">

  
  
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

  
  

  
<script>
var _hmt = _hmt || [];
(function() {
	var hm = document.createElement("script");
	hm.src = "https://hm.baidu.com/hm.js?aa994a8d65700b8835787dd39d079d7e";
	var s = document.getElementsByTagName("script")[0]; 
	s.parentNode.insertBefore(hm, s);
})();
</script>


</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-redisForPrinciple1"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  Redis基本介绍
</h1>
 

    </header>
     
    <div class="article-meta">
      <a href="/2021/08/24/redisForPrinciple1/" class="article-date">
  <time datetime="2021-08-24T14:11:37.000Z" itemprop="datePublished">2021-08-24</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/redis/">redis</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">4.9k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">18 分钟</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <p>Redis基本介绍</p>
<a id="more"></a>

<p>为什么叫REDIS呢？它的全称是REmote Dlctionary Service，直接翻译过来是远程字典服务。</p>
<h1 id="Redis定位与特性"><a href="#Redis定位与特性" class="headerlink" title="Redis定位与特性"></a>Redis定位与特性</h1><h2 id="SQL与NoSQL"><a href="#SQL与NoSQL" class="headerlink" title="SQL与NoSQL"></a>SQL与NoSQL</h2><p>在绝大部分时候，我们都会首先考虑用关系型数据库来存储业务数据，比如SQLServer，Oracle，MySQL等等。</p>
<p>这些关系型数据库的特点：</p>
<p>1、它以表格的形式，基于行存储数据，是一个二维的模式。</p>
<p>2、它存储的是结构化的数据，数据存储有固定的模式（schema），数据需要适应表结构。</p>
<p>3、表与表之间存在关联（Relationship）。</p>
<p>4、大部分关系型数据库都支持SQL（结构化查询语言）的操作，支持复杂的关联查询。</p>
<p>5、通过支持事务（ACID）来提供严格或者实时的数据一致性。</p>
<p>但是使用关系型数据库也存在一些限制，比如：</p>
<p>1、要实现扩容的话，只能向上（垂直）扩展，比如磁盘限制了数据的存储，就要扩大磁盘容量，通过堆硬件的方式，不支持动态的扩缩容，水平扩容需要复杂的技术来实现，比如分库分表。</p>
<p>2、表结构修改困难，因此存储的数据格式也受到限制。</p>
<p>3、关系型数据库通常会把数据持久化到磁盘，在高并发和高数据量的情况下，基于磁盘的读写压力比较大。</p>
<p>为了规避关系型数据库的一系列问题，就有了非关系型的数据库，我们一般把它叫做“non-relational”或者“Not Only SQL”。NoSQL最开始是不提供SQL（Structured Query Language结构化查询语言）的数据库的意思，但是后来意思慢慢地发生了变化。</p>
<p>非关系型数据库的特点：</p>
<p>1、存储非结构化的数据，比如文本、图片、音频、视频。</p>
<p>2、表与表之间没有关联，可扩展性强。</p>
<p>3、保证数据的最终一致性，遵循BASE理论，Basically Available（基本可用）、Soft-state（软状态）、Eventually Consistent（最终一致性）。</p>
<p>4、支持海量数据的存储和高并发的高效读写。</p>
<p>5、支持分布式，能够对数据进行分片存储，扩缩容简单。</p>
<p>对于不同的存储类型，又有各种各样的非关系型数据库，比如有几种常见的类型：</p>
<p>1、KV存储：Redis和Memcached。</p>
<p>2、文档存储：MongoDB。</p>
<p>3、列存储：HBase。</p>
<p>4、图存储：Neo4j。</p>
<p>5、对象存储。</p>
<p>6、XML存储等等等等。</p>
<p>这个网页列举了各种各样的NoSQL数据库<a href="http://nosql-database.org/" target="_blank" rel="noopener">http://nosql-database.org/</a></p>
<p>能不能把SQL和NoSQL的特性结合在一起呢？</p>
<p>当然可以。所以现在有有了所谓的NewSQL数据库。</p>
<p>NewSQL结合了SQL和NoSQL的特性。例如TiDB（PingCAP）、VoltDB、ScaleDB。</p>
<h2 id="Redis特性"><a href="#Redis特性" class="headerlink" title="Redis特性"></a>Redis特性</h2><p>对于Redis，大部分时候的认识是一个缓存的组件，当然从它的发展历史也可以看到，它最开始并不是作为缓存使用的。只是在很多的互联网应用里面，它作为缓存发挥了最大的作用。</p>
<p>Redis的主要特性有哪些，为什么要使用它作为数据库的缓存。</p>
<p>如果要了解Redis的特性，先了解几个问题：</p>
<p>1、为什么要把数据放在内存中？</p>
<p>1）内存的速度更快，10wQPS</p>
<p>2）减少计算的时间，减轻数据库压力</p>
<p>2、如果是用内存的数据结构作为缓存，为什么不用HashMap或者Memcached？</p>
<p>1）更丰富的数据类型</p>
<p>2）支持多种编程语言</p>
<p>3）功能丰富：持久化机制、内存淘汰策略、事务、发布订阅、pipeline、lua</p>
<p>4）支持集群、分布式</p>
<p>Memcached和Redis的主要区别是什么？</p>
<p>Memcached只能存储KV、没有持久化机制、不支持主从复制、是多线程的。</p>
<p>redis 安装启动<a href="https://dxysun.com/2018/04/23/redisForRemote/">参考</a></p>
<p>Redis的存储叫做key-value存储，或者叫做字典结构。key的最大长度限制是他512M，值的限制不同，有的是用长度限制的，有的是用个数限制的。</p>
<h1 id="Redis基本数据类型"><a href="#Redis基本数据类型" class="headerlink" title="Redis基本数据类型"></a>Redis基本数据类型</h1><p>Redis一共有8种数据类型</p>
<p>String、 Hash、 Set、 List、Zset、Hyperloglog、Geo、 Streams</p>
<h2 id="String字符串"><a href="#String字符串" class="headerlink" title="String字符串"></a>String字符串</h2><h3 id="存储类型"><a href="#存储类型" class="headerlink" title="存储类型"></a>存储类型</h3><p>可以用来存储int（整数）、float（单精度浮点数）、String（字符串）。</p>
<h3 id="操作命令"><a href="#操作命令" class="headerlink" title="操作命令"></a>操作命令</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#设值</span></span><br><span class="line"><span class="built_in">set</span> abc 123456</span><br><span class="line"></span><br><span class="line"><span class="comment">#获取指定范围的字符</span></span><br><span class="line">getrange abc 0 1</span><br><span class="line"></span><br><span class="line"><span class="comment">#获取值长度</span></span><br><span class="line">strlen abc</span><br><span class="line"></span><br><span class="line"><span class="comment">#字符串追加内容</span></span><br><span class="line">append abc good</span><br><span class="line"></span><br><span class="line"><span class="comment">#设置多个值（批量操作，原子性）</span></span><br><span class="line">mset def 2673 ghi 666</span><br><span class="line"></span><br><span class="line"><span class="comment">#获取多个值</span></span><br><span class="line">mget def ghi</span><br><span class="line"></span><br><span class="line"><span class="comment">#设置值，如果key存在，则不成功</span></span><br><span class="line">setnx key1 pyy</span><br><span class="line"><span class="comment">#基于此可实现分布式锁，用del key释放锁。</span></span><br><span class="line"><span class="comment">#但如果释放锁的操作失败了，导致其他节点永远获取不到锁，怎么办？</span></span><br><span class="line"><span class="comment">#加过期时间。单独用expire加过期，也失败了，无法保证原子性，怎么办？多参数</span></span><br><span class="line"><span class="built_in">set</span> key value [expiration EX seconds|PX milliseconds][NX|XX]</span><br><span class="line"><span class="comment">#使用参数的方式</span></span><br><span class="line"><span class="built_in">set</span> kl vl EX 10 NX</span><br><span class="line"></span><br><span class="line"><span class="comment">#（整数）值递增（值不存在会得到1） </span></span><br><span class="line">incr k2</span><br><span class="line">incrby k2 100</span><br><span class="line"></span><br><span class="line"><span class="comment">#（整数）值递减</span></span><br><span class="line">decr k2</span><br><span class="line">decrby k2 100 <span class="comment">#浮点数增量</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">set</span> mf 2.6</span><br><span class="line">incrbyfloat mf 7.3</span><br></pre></td></tr></table></figure>



<h3 id="存储（实现）原理"><a href="#存储（实现）原理" class="headerlink" title="存储（实现）原理"></a>存储（实现）原理</h3><p>Redis是KV的数据库，Key-Value一般会用哈希表来存储它， Redis的最外层确实是通过hashtable实现的（这个叫做外层的哈希）。</p>
<p>在Redis里面，这个哈希表怎么实现呢？</p>
<p>看一下C语言的源码（dict.h47行），每个键值对都是一个dictEntry（怪不得叫远程字典服务），通过指针指向key的存储结构和value的存储结构，而且next存储了指向下一个键值对的指针。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">redisDb</span> &#123;</span> </span><br><span class="line">dict *dict;  <span class="comment">/*所有的键值对*/</span><span class="comment">/*The keyspace for this DB*/</span></span><br><span class="line">dict *expires;  <span class="comment">/*设置了过期时间的键值对//*Timeout of keys with a timeout set*/</span></span><br><span class="line">dict *blocking_keys;  <span class="comment">/* Keys with clients waiting for data (BLPOP)*/</span></span><br><span class="line">dict *ready_keys; <span class="comment">/* Blocked keys that received aPUSH*</span></span><br><span class="line"><span class="comment">dict *watched _ keys; /* WATCHED keys for MULTI/EXEC CAS */</span></span><br><span class="line"><span class="keyword">int</span> id; <span class="comment">/* Database ID */</span></span><br><span class="line"><span class="keyword">long</span> <span class="keyword">long</span> avg_ttl; <span class="comment">/* Average TTL,just for stats */</span></span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> expires_cursor;<span class="comment">/* Cursor of the active expire cycle.*/</span></span><br><span class="line"><span class="built_in">list</span> *defrag_later; <span class="comment">/* List of key names to attempt to defrag one by one, gradually.*/</span></span><br><span class="line">&#125; redisDb;</span><br></pre></td></tr></table></figure>

<p>以set hello word为例，因为key是字符串，Redis自己实现了一个字符串类型，叫做SDS （Simple Dynamic String简单动态字符串），所以hello指向一个SDS的结构。</p>
<p>value是 world，同样是一个字符串，当 value存储一个字符串的时候, Redis并没有直接使用SDS存储，而是存储在 redisObject中。实际上五种常用的数据类型的任何一种的 value，都是通过 redisObject来存储的，最终 redisObject再通过一个指针指向实际的数据结构，比如字符串或者其他。</p>
<p><img src="https://tu.dxysun.com/20210824230717-20210824230718.png" alt=""></p>
<p>SDS定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">struct__attribute__(_ packed __)) sdshdr8&#123;</span><br><span class="line"><span class="keyword">uint8_t</span> len; <span class="comment">/*当前字符数组的长度*/</span></span><br><span class="line"><span class="keyword">uint8_t</span> alloc; <span class="comment">/*当前字符数组总共分配的内存大小</span></span><br><span class="line"><span class="comment">unsigned char flags; /*当前字符数组的属性、用来标识到底是sdshdr8还是sdshdr16等*/</span> </span><br><span class="line"><span class="keyword">char</span> buf[]; <span class="comment">/*字符串真正的值*/</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>SDS 本质上其实还是字符数组。</p>
<p>SDS又有多种结构（sds.h）：sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64，用于存储不同的长度的字符串，分别代表2^5=32byte，2^8=256byte，2^16=65536byte=64KB，2^32byte=4GB。 </p>
<p>因为C语言本身没有字符串类型，只能用字符数组char[] 实现。</p>
<p>1、使用字符数组必须先给目标变量分配足够的空间，否则可能会溢出。</p>
<p>2、如果要获取字符长度，必须遍历字符数组，时间复杂度是O（n）。</p>
<p>3、C字符串长度的变更会对字符数组做内存重分配。</p>
<p>4、通过从字符串开始到结尾碰到的第一个”\0”来标记字符串的结束，因此不能保存图片、音频、视频、压缩文件等二进制（bytes）保存的内容，二进制不安全。</p>
<p>SDS的特点：</p>
<p>1、不用担心内存溢出问题，如果需要会对SDS进行扩容。</p>
<p>2、获取字符串长度时间复杂度为O（1），因为定义了len属性。</p>
<p>3、通过“空间预分配”（sdsMakeRoomFor）和“惰性空间释放”，防止多次重分配内存。</p>
<p>4、判断是否结束的标志是len属性，可以包含”\0”（它同样以”\0”结尾是因为这样就可以使用C语言中函数库操作字符串的函数了）。</p>
<h3 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h3><h4 id="缓存"><a href="#缓存" class="headerlink" title="缓存"></a>缓存</h4><p>String类型，缓存热点数据。例如明星出轨、网站首页、报表数据等等。 可以显著提升热点数据的访问速度。</p>
<h4 id="分布式数据共享"><a href="#分布式数据共享" class="headerlink" title="分布式数据共享"></a>分布式数据共享</h4><p>String类型，因为Redis是分布式的独立服务，可以在多个应用之间共享例如：分布式Session</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependenecy</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">groupld</span>&gt;</span>org.springframework.session<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span> </span><br><span class="line"><span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-session-data-redis<span class="tag">&lt;/<span class="name">artifactld</span>&gt;</span> </span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<h4 id="分布式锁"><a href="#分布式锁" class="headerlink" title="分布式锁"></a>分布式锁</h4><p>String类型的setnx方法，只有不存在时才能添加成功，返回true。</p>
<h4 id="全局ID"><a href="#全局ID" class="headerlink" title="全局ID"></a>全局ID</h4><p>INT类型，INCRBY，利用原子性</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">incrby userid 1000</span><br></pre></td></tr></table></figure>

<p>分库分表的场景，一次性拿一段</p>
<h4 id="计数器"><a href="#计数器" class="headerlink" title="计数器"></a>计数器</h4><p>INT类型，INCR方法<br>例如：文章的阅读量，微博点赞数，允许一定的延迟，先写入Redis再定时同步到数据库。</p>
<h4 id="限流"><a href="#限流" class="headerlink" title="限流"></a>限流</h4><p>INT类型，INCR方法<br>以访问者的IP和其他信息作为key，访问一次增加一次计数，超过次数则返回false。</p>
<p>总结一下：利用redis本身的特性，和String内存的存储内容，以及提供的操作方法，我们可以用来达到很多的业务目的。</p>
<h2 id="Hash哈希"><a href="#Hash哈希" class="headerlink" title="Hash哈希"></a>Hash哈希</h2><h3 id="存储类型-1"><a href="#存储类型-1" class="headerlink" title="存储类型"></a>存储类型</h3><p>Hash用来存储多个无序的键值对。最大存储数量2^32-1（40亿左右）</p>
<p><img src="https://tu.dxysun.com/20210824232240-20210824232240.png" alt=""></p>
<p>注意：前面说Redis所有的KV本身就是键值对，用dictEntry实现的，叫做外层的哈希，现在讲的是内层的哈希。</p>
<p>Hash的value只能是字符串，不能嵌套其他类型，比如hash或者list。</p>
<p>同样是存储字符串，Hash与String的主要区别</p>
<p>1，把所有相关的值聚集到一个key中，节省内存空间</p>
<p>2、只使用一个key，减少key冲突</p>
<p>3、当需要批量获取值的时候，只需要使用一个命令，减少内存/IO/CPU的消耗</p>
<p>Hash不适合的场景：</p>
<p>1、Field不能单独设置过期时间</p>
<p>2、需要考虑数据量分布的问题（field非常多的时候，无法分布到多个节点）</p>
<h3 id="操作命令-1"><a href="#操作命令-1" class="headerlink" title="操作命令"></a>操作命令</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">hset hl f 6</span><br><span class="line">hset hl e 5</span><br><span class="line">hmset hl a 1 b 2 c 3 d 4</span><br><span class="line">hget hl a</span><br><span class="line">hmget hl a b c d </span><br><span class="line">hkeys hl</span><br><span class="line">hvals hl</span><br><span class="line">hgetall hl</span><br><span class="line">hdel h1 a</span><br><span class="line">hlen hl</span><br></pre></td></tr></table></figure>

<h3 id="存储（实现）原理-1"><a href="#存储（实现）原理-1" class="headerlink" title="存储（实现）原理"></a>存储（实现）原理</h3><p>Redis的Hash本身也是一个KV的结构，是不是跟外层的哈希一样，用dictEntry 实现呢？<br>内层的哈希底层可以使用两种数据结构实现：</p>
<p>ziplist：OBJ ENCODING ZIPLIST（压缩列表） </p>
<p>hashtable：OBJENCODINGHT（哈希表）</p>
<h4 id="ziplist压缩列表"><a href="#ziplist压缩列表" class="headerlink" title="ziplist压缩列表"></a>ziplist压缩列表</h4><p>ziplist是一个经过特殊编码的，由连续内存块组成的双向链表。</p>
<p>它不存储指向上一个链表节点和指向下一个链表节点的指针，而是存储上一个节点长度和当前节点长度。这样读写可能会慢一些，因为要去算长度，但是可以节省内存，是一种时间换空间的思想。</p>
<p><img src="https://tu.dxysun.com/image-20210824233101343-20210824233102.png" alt="image-20210824233101343"></p>
<p>什么时候使用ziplist存储？</p>
<p>当hash对象同时满足以下两个条件的时候，使用ziplist编码：</p>
<p>1）哈希对象保存的键值对数量 &lt; 512个</p>
<p>2）所有的键值对的健和值的字符串长度都 &lt; 64 byte（一个英文字母一个字节）。</p>
<p>src/redis.conf配置</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">hash-max-ziplist-value</span> <span class="string">64 //ziplist 中最大能存放的值长度</span></span><br><span class="line"><span class="meta">hash-max-ziplist-entries</span> <span class="string">512 //ziplist中最多能存放的entry节点数量</span></span><br></pre></td></tr></table></figure>

<p>如果超过这两个阈值的任何一个，存储结构就会转换成hashtable。</p>
<p>总结：字段个数少，字段值小，用ziplist。</p>
<h4 id="hashtable-dict"><a href="#hashtable-dict" class="headerlink" title="hashtable (dict)"></a>hashtable (dict)</h4><p>在Redis中，hashtable被称为字典（dictionary）。</p>
<p>前面我们知道了，Redis的KV结构是通过一个dictEntry来实现的。在hashtable中，又对dictEntry进行了多层的封装。</p>
<p><img src="https://tu.dxysun.com/image-20210824233438933-20210824233440.png" alt="image-20210824233438933"></p>
<p>注意：dictht后面是NULL说明第二个ht还没用到。dictEntry 后面是NULL说明没有hash到这个地址。dictEntry后面是NULL说明没有发生哈希冲突。</p>
<h3 id="应用场景-1"><a href="#应用场景-1" class="headerlink" title="应用场景"></a>应用场景</h3><p>存储对象类型的数据</p>
<p>比如对象或者一张表的数据，比String节省了更多key的空间，也更加便于集中管理。</p>
<h2 id="List列表"><a href="#List列表" class="headerlink" title="List列表"></a>List列表</h2><h3 id="存储类型-2"><a href="#存储类型-2" class="headerlink" title="存储类型"></a>存储类型</h3><p>存储有序的字符串（从左到右），元素可以重复。最大存储数量2^32-1（40亿左右）。<br><img src="https://tu.dxysun.com/image-20210824234120747-20210824234141.png" alt="image-20210824234120747"></p>
<h3 id="操作命令-2"><a href="#操作命令-2" class="headerlink" title="操作命令"></a>操作命令</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#元素增减：</span></span><br><span class="line">lpush queue a</span><br><span class="line">lpush queue b c </span><br><span class="line">rpush queue d e </span><br><span class="line">lpop queue</span><br><span class="line">rpop queue</span><br><span class="line"><span class="comment">#取值</span></span><br><span class="line">lindex queue 0 </span><br><span class="line">lrange queue 0 1</span><br></pre></td></tr></table></figure>

<p><img src="https://tu.dxysun.com/image-20210824234433051-20210824234434.png" alt="image-20210824234433051"></p>
<h3 id="存储（实现）原理-2"><a href="#存储（实现）原理-2" class="headerlink" title="存储（实现）原理"></a>存储（实现）原理</h3><p>在早期的版本中，数据量较小时用ziplist存储（特殊编码的双向链表），达到临界值时转换为linkedlist进行存储，分别对应OBJ_ENCODING_ZIPLIST和OBJ ENCODING LINKEDLIST。</p>
<p>3.2版本之后，统一用quicklist来存储。quicklist存储了一个双向链表，每个节点都是一个ziplist，所以是ziplist和linkedlist的结合体。</p>
<h4 id="quicklist"><a href="#quicklist" class="headerlink" title="quicklist"></a>quicklist</h4><p><img src="https://tu.dxysun.com/image-20210824234612188-20210824234614.png" alt="image-20210824234612188"></p>
<h3 id="应用场景-2"><a href="#应用场景-2" class="headerlink" title="应用场景"></a>应用场景</h3><p>List主要用在存储有序内容的场景。</p>
<p>例如用户的消息列表、网站的公告列表、活动列表、博客的文章列表、评论列表等。</p>
<p>队列/栈</p>
<p>List还可以当做分布式环境的队列/栈使用。</p>
<p>List提供了两个阻塞的弹出操作：BLPOP/BRPOP，可以设置超时时间（单位：秒）。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">blpop queue</span><br><span class="line">brpop queue</span><br></pre></td></tr></table></figure>

<p>BLPOP：BLPOP key1 timeout移出并获取列表的第一个元素，如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。</p>
<p>BRPOP：BRPOP key1 timeout移出并获取列表的最后一个元素，如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。</p>
<p>队列：先进先出，rpush blpop，左头右尾，右边进入队列，左边出队列。</p>
<p>栈：先进后出，rpush brpop</p>
<p>总结一下：</p>
<p>List存储有序的内容，用quicklist实现，本质上是数组+链表。</p>
<p>Hashtable也是数组加链表，只是内部编码结构不一样。</p>
<h2 id="Set集合"><a href="#Set集合" class="headerlink" title="Set集合"></a>Set集合</h2><h3 id="存储类型-3"><a href="#存储类型-3" class="headerlink" title="存储类型"></a>存储类型</h3><p>Set存储String类型的无序集合，最大存储数量2^32-1（40亿左右）。</p>
<h3 id="操作命令-3"><a href="#操作命令-3" class="headerlink" title="操作命令"></a>操作命令</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">//添加一个或者多个元素 </span><br><span class="line">sadd myset a b c d e f g </span><br><span class="line">//获取所有元素</span><br><span class="line">smembers myset</span><br><span class="line">//统计元素个数</span><br><span class="line">scard myset</span><br><span class="line">//随机获取一个元素 </span><br><span class="line">srandmember myset</span><br><span class="line">//随机弹出一个元素</span><br><span class="line">spop myset</span><br><span class="line">从移除一个或者多个元素</span><br><span class="line">srem myset d e f</span><br><span class="line">//查看元素是否存在</span><br><span class="line">sismember myset a</span><br></pre></td></tr></table></figure>



<h3 id="存储（实现）原理-3"><a href="#存储（实现）原理-3" class="headerlink" title="存储（实现）原理"></a>存储（实现）原理</h3><p>Redis用intset或hashtable存储set。如果元素都是整数类型，就用inset存储。 </p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">intset</span>&#123;</span></span><br><span class="line"><span class="keyword">uint32_t</span> encoding；<span class="comment">//编码类型intl6_t、int32_t、int64_t </span></span><br><span class="line">uint32_tlength；<span class="comment">//长度最大长度：2^32</span></span><br><span class="line"><span class="keyword">int8_t</span> contents[]；<span class="comment">//用来存储成员的动态数组</span></span><br><span class="line">&#125; intset;</span><br></pre></td></tr></table></figure>

<p>如果不是整数类型，就用hashtable（数组+链表的存来储结构）。</p>
<p>如果元素个数超过512个，也会用hashtable存储。跟一个配置有关：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">set-max-intset-entries 512</span><br></pre></td></tr></table></figure>

<p>set的key没有value，怎么用hashtable存储？value存null就好了。</p>
<h3 id="应用场景-3"><a href="#应用场景-3" class="headerlink" title="应用场景"></a>应用场景</h3><p>抽奖</p>
<p>随机获取元素：spop myset </p>
<p>点赞、签到、打卡</p>
<p>商品标签</p>
<h2 id="ZSet有序集合"><a href="#ZSet有序集合" class="headerlink" title="ZSet有序集合"></a>ZSet有序集合</h2><h3 id="存储类型-4"><a href="#存储类型-4" class="headerlink" title="存储类型"></a>存储类型</h3><p>sorted set存储有序的元素，每个元素有个score，按照score从小到大排名，score相同时，按照key的ASCII码排序。</p>
<p><img src="https://tu.dxysun.com/image-20210824235845949-20210824235847.png" alt="image-20210824235845949"></p>
<p>数据结构对比</p>
<p><img src="https://tu.dxysun.com/image-20210824235913394-20210824235914.png" alt="image-20210824235913394"></p>
<h3 id="操作命令-4"><a href="#操作命令-4" class="headerlink" title="操作命令"></a>操作命令</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;添加元素</span><br><span class="line">zadd myzset l0 java 20 php 30 ruby 40 cpp 50 python </span><br><span class="line">&#x2F;&#x2F;获取全部元素</span><br><span class="line">zrange myzset 0 l withscores</span><br><span class="line">zrevrange myzset 0 l withscores</span><br><span class="line">&#x2F;&#x2F;根据分值区间获取元素</span><br><span class="line">zrangebyscore myzset 20 30</span><br><span class="line">&#x2F;&#x2F;从移除元素也可以根据score rank删除</span><br><span class="line">zrem myzset php cpp</span><br><span class="line">&#x2F;&#x2F;统计元素个数</span><br><span class="line">zcard myzset</span><br><span class="line">&#x2F;&#x2F;分值递增</span><br><span class="line">zincrby myzset 5 python</span><br><span class="line">&#x2F;&#x2F;根据分值统计个数</span><br><span class="line">zcount myzset 20 60</span><br><span class="line">&#x2F;&#x2F;获取元素rank</span><br><span class="line">zrank myzset python</span><br><span class="line">&#x2F;&#x2F;获取元素score</span><br><span class="line">zscore myzset python</span><br><span class="line">&#x2F;&#x2F;也有倒序的rev操作（reverse）</span><br></pre></td></tr></table></figure>



<h3 id="存储（实现）原理-4"><a href="#存储（实现）原理-4" class="headerlink" title="存储（实现）原理"></a>存储（实现）原理</h3><p>默认使用ziplist编码</p>
<p>在ziplist的内部，按照score排序递增来存储，插入的时候要移动之后的数据。如果元素数量大于等于128个，或者任一member长度大于等于64字节，使用 skiplist+dict存储。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">zset-max-ziplist-entries l28 </span><br><span class="line">zset-max-ziplist-value 64</span><br></pre></td></tr></table></figure>

<p>skiplist （跳表）</p>
<p>先来看一下有序链表：</p>
<p><img src="https://tu.dxysun.com/image-20210825000325463-20210825000326.png" alt="image-20210825000325463"></p>
<p>在这样一个链表中，如果要查找某个数据，那么需要从头开始逐个进行比较直到找到包含数据的那个节点，或者找到第一个比给定数据大的节点为止，时间复杂度为O（n）。同样，当我们要插入新数据的时候，也要经历同样的查找过程，从而确定插入个位置。二分查找法只适用于有序数组，不适用于链表。</p>
<p>假如每相邻两个节点增加一个指针，让指针指向下个节点（或者理解为有三个元素进入了第二层）。</p>
<p><img src="https://tu.dxysun.com/image-20210825000402081-20210825000403.png" alt="image-20210825000402081"></p>
<p>这样所有新增加的指针连成了一个新的链表，但它包含的节点个数只有原来的一半（上图中是7，19，26）。</p>
<p><img src="https://tu.dxysun.com/image-20210825000553029-20210825000554.png" alt="image-20210825000553029"></p>
<p>比如，想查找23，查找的路径是沿着标红的指针所指向的方向进行的：</p>
<p>1、23首先和7比较，再和19比较，比它们都大，继续向后比较。 </p>
<p>2、但23和26比较的时候，比26要小，因此回到下面的链表（原链表） ，与19 在第一层的下一个节点22比较。</p>
<p>3、23比22要大，沿下面的指针继续向后和26比较。23比26小，说明待查数据23在原链表中不存在。</p>
<p>在这个查找过程中，由于新增加的指针，我们不再需要与链表中每个节点逐个进行比较了。需要比较的节点数大概只有原来的一半，这就是跳跃表。</p>
<p>为什么不用AVL树或者红黑树？因为skiplist更加简洁。</p>
<p>因为level是随机的，得到的skiplist可能是这样的，有些在第四层，有些在第三层，有些在第二层，有些在第一层。</p>
<p><img src="https://tu.dxysun.com/image-20210825000752075-20210825000753.png" alt="image-20210825000752075"></p>
<h3 id="应用场景-4"><a href="#应用场景-4" class="headerlink" title="应用场景"></a>应用场景</h3><p>顺序会动态变化的列表，例如百度热榜、微博热搜。</p>
<h2 id="其他数据类型"><a href="#其他数据类型" class="headerlink" title="其他数据类型"></a>其他数据类型</h2><h3 id="BitMaps"><a href="#BitMaps" class="headerlink" title="BitMaps"></a>BitMaps</h3><p>Bitmaps是在字符串类型上面定义的位操作，一个字节由8个二进制位组成。</p>
<p><img src="https://tu.dxysun.com/image-20210825001027095-20210825001028.png" alt="image-20210825001027095"></p>
<h3 id="Hyperloglogs"><a href="#Hyperloglogs" class="headerlink" title="Hyperloglogs"></a>Hyperloglogs</h3><p>Hyperloglogs：提供了一种不太精确的基数统计方法，用来统计一个集合中不重复的元素个数，比如统计网站的UV，或者应用的日活、月活，存在一定的误差。</p>
<h3 id="Geo"><a href="#Geo" class="headerlink" title="Geo"></a>Geo</h3><p>用于存储经纬度</p>
<p>可以增加地址位置信息、获取地址位置信息、计算两个位置的距离、获取指定范围内的地理位置集合等等。</p>
<h3 id="Streams"><a href="#Streams" class="headerlink" title="Streams"></a>Streams</h3><p>5.0推出的数据类型，支持多播的可持久化的消息队列，用于实现发布订阅功能，借鉴了kafka的设计。</p>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><h2 id="数据结构总结"><a href="#数据结构总结" class="headerlink" title="数据结构总结"></a>数据结构总结</h2><p><img src="https://tu.dxysun.com/image-20210825001402299-20210825001403.png" alt="image-20210825001402299"></p>
<h2 id="编码转换总结"><a href="#编码转换总结" class="headerlink" title="编码转换总结"></a>编码转换总结</h2><p><img src="https://tu.dxysun.com/image-20210825001505598-20210825001506.png" alt="image-20210825001505598"></p>
<h2 id="应用场景总结"><a href="#应用场景总结" class="headerlink" title="应用场景总结"></a>应用场景总结</h2><p>缓存提升热点数据的访问速度</p>
<p>共享数据</p>
<p>数据的存储和共享的问题</p>
<p>全局ID</p>
<p>分布式全局ID的生成方案（分库分表）</p>
<p>分布式锁进程间共享数据的原子操作保证</p>
<p>在线用户统计和计数</p>
<p>队列、栈</p>
<p>跨进程的队列/栈</p>
<p>消息队列</p>
<p>异步解耦的消息机制</p>
<p>服务注册与发现一RPC通信机制的服务协调中心（Dubbo支持Redis）</p>
<p>购物车</p>
<p>新浪/Twitter用户消息时间线</p>
<p>泡学抽奖逻辑（礼物、转发）</p>
<p>点赞、签到、打卡</p>
<p>商品标签</p>
<p>用户（商品）关注（推荐）模型电商产品筛选</p>
<p>排行榜</p>
 
      <!-- reward -->
      
      <div id="reword-out">
        <div id="reward-btn">
          打赏
        </div>
      </div>
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=https://dxysun.com/2021/08/24/redisForPrinciple1/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/redis/" rel="tag">redis</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2021/08/25/redisForPrinciple2/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            redis高级特性和相关原理
          
        </div>
      </a>
    
    
      <a href="/2021/08/23/mysqlForPrinciple3/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">Mysql之事务详解</div>
      </a>
    
  </nav>

  
   
  
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2024
        <i class="ri-heart-fill heart_icon"></i> dxysun
      </li>
    </ul>
    <ul>
      <li>
        
        
        
        由 <a href="https://hexo.io" target="_blank">Hexo</a> 强力驱动
        <span class="division">|</span>
        主题 - <a href="https://github.com/Shen-Yu/hexo-theme-ayer" target="_blank">Ayer</a>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
        <li>
          <a href="https://beian.miit.gov.cn" target="_black" rel="nofollow">豫ICP备17012675号-1</a>
        </li>
        
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="https://dxysun.com/static/logo.png" alt="迎着朝阳"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/photos">相册</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="https://tu.dxysun.com/alipay-20201219151322.jpg">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="https://tu.dxysun.com/weixin-20201219151346.png">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->


<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: 'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto'
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
      tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
          processEscapes: true,
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
      }
  });

  MathJax.Hub.Queue(function() {
      var all = MathJax.Hub.getAllJax(), i;
      for(i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
      }
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/mathjax@2.7.6/unpacked/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script>
  var ayerConfig = {
    mathjax: true
  }
</script>

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


<script src="/js/dz.js"></script>



    
  </div>
</body>

</html>